package parser;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;

import math.Expression;
import math.Relation;
import model.AltaRicaDomain;
import model.AltaRicaNode;
import model.altarica_fields.Event;
import model.altarica_fields.Sync;
import model.altarica_fields.Sync.SyncElement;
import model.altarica_fields.Trans;
import model.altarica_fields.UpdateExpression;
import model.altarica_fields.Var;
import model.altarica_fields.VarType;
import util.BinaryTree;

import common.AltaRicaModel;

public class AltaRicaParseNode {
	
	private String name;
	
	private List<Var> state_list;
	private List<Var> flow_list;
	private List<Event> event_list;
	private List<String[]> sub_list;
	private List<Sync> sync_list;
	private List<Trans> trans_list;
	private List<UpdateExpression> init_list;
	private List<Expression> assert_list;
	private List<String[]> priorites_list;
	
	private AltaRicaNode anode;
	
	private boolean compiled = false;
	
	public AltaRicaParseNode(){
		
		name = null;
		
		state_list = new LinkedList<Var>();
		flow_list = new LinkedList<Var>();
		event_list = new LinkedList<Event>();
		sub_list = new LinkedList<String[]>();
		sync_list = new LinkedList<Sync>();
		trans_list = new LinkedList<Trans>();
		init_list = new LinkedList<UpdateExpression>();
		assert_list = new LinkedList<Expression>();
		priorites_list = new LinkedList<String[]>();
		
		anode = null;
	}
	
	public void setName(String name){
		
		this.name = name;
	}
	
	public String getName(){
		
		return name;
	}
	
	public void addState(Var v){
		
		state_list.add(v);
	}
	
	public void addFlow(Var v){
		
		flow_list.add(v);
	}
	
	public void addEvent(String id){
		
		event_list.add(new Event(id));
	}
	
	public void addSub(String sub[]){
		
		sub_list.add(sub);
	}
	
	public void addSync(Sync sync){
		
		sync_list.add(sync);
	}
	
	public void addTrans(Trans t){
		
		trans_list.add(t);
	}
	
	public void addInit(UpdateExpression u){
		
		init_list.add(u);
	}
	
	public void addPriorities(List<String> less, List<String> more) {
		
		String str[] = new String[2];
		
		for (String l : less) {
			for (String m : more) {
				str = new String[2];
				str[0] = l;
				str[1] = m;
				priorites_list.add(str);
			}
		}
	}
	
	public void addAssert(Expression expr) {
		
		assert_list.add(expr);
	}
	
	public boolean isCompiled() {
		
		return this.compiled;
	}
	
	public AltaRicaNode getNode() {
		
		return this.anode;
	}
	
	public AltaRicaNode compile(AltaRicaParseModel pmodel) {
		
		AltaRicaNode node = new AltaRicaNode();
		
		this.anode = node;
		
		Hashtable<String, Var> states = new Hashtable<String, Var>();
		Hashtable<String, Var> flow = new Hashtable<String, Var>();
		Hashtable<String, Event> events = new Hashtable<String, Event>();
		
		node.setName(this.name);
		
		for (Var v : state_list) {
			
			compileVar(pmodel,node,v);
			if (states.put(v.getId(), v) != null) {
				System.out.println("Duplicate Variable " + v);
			}
		}
		
		node.setStates(states);
		
		for (Var v : flow_list) {
			
			compileVar(pmodel,node,v);
			if ((flow.put(v.getId(), v) != null) || states.contains(v.getId())) {
				System.out.println("Duplicate Variable " + v);
			}
		}

		node.setFlows(flow);
		
		for (Event e : event_list) {
			events.put(e.getId(), e);
		}
		
		if (events.contains(Event.getEpsilon().getId()) == false) {
			events.put(Event.getEpsilon().getId(), Event.getEpsilon());
		}
		
		node.setEvents(events);
		
		compilePriorities(pmodel,node);
		
		for (String[] str : sub_list) {
			
			AltaRicaNode sub = null;
			
			AltaRicaParseNode pnode = pmodel.getParsedNodes().get(str[1]);
			if (pnode.isCompiled() == false) {
				sub = pnode.compile(pmodel);
			} else {
				sub = pmodel.getParsedNodes().get(str[1]).anode;
			}
			node.addSub(str[0], sub);
		}
		
		for (Sync sync : sync_list) {
			
			compileSync(pmodel, node, sync);
		}
		
		compileUpdates(node,init_list,true);
		
		node.setInit(init_list);
		
		compileTransitions(node);
		
		node.setTrans(trans_list);
		
		for (Expression expr : assert_list) {
			
			compileExpression(node,expr);
		}
		
		node.setAsserts(assert_list);
		
		this.compiled = true;
		
		return node;
	}
	
	public void compileRelation(Relation p) {
		
		AltaRicaNode node = AltaRicaModel.getNode(p.getNode().getName());
		
		if (node == null) {
			System.out.println("Error: unknown AltaRica node: " + p.getNode().getName());
			return;
		}
		
		p.setNode(node);
		
		BinaryTree tree = p.getExpression().getExpression();
		
		compileRelationExpression(node, tree);
	}
	
	private void compilePriorities(AltaRicaParseModel pmodel, AltaRicaNode node) {
		
		for (String str[] : priorites_list) {
			
			Event a = node.getEvent(str[0]);
			Event b = node.getEvent(str[1]);
			
			b.addLessEvent(a);
		}
	}
	
	private void compileSync(AltaRicaParseModel pmodel, AltaRicaNode node, Sync sync) {
		
		AltaRicaNode sub  = node;
		
		ArrayList<SyncElement> syncs = sync.getSyncs();
		
		ArrayList<String> ids = null;
		
		String evid = null;

		for (SyncElement synce : syncs) {
			
			sub = node;
			
			ids = synce.getSubs();
			
			evid = ids.get(ids.size() - 1);
			
			ids.remove(ids.size() -1);
				
			for (String id : ids) {
				sub = sub.getSub(id);
				if (sub == null) {
					System.out.println("Missing Sub " + id);
					return ;
				}
			}
			
			Event e = sub.getEvent(evid);
			
			if (e == null) {
				System.out.println("Unknown Event " + evid + " in " + sync);
			}
			
			sync.setEvent(synce,e);
		}
		
		node.addSync(sync);
	}
	
	private void compileVar(AltaRicaParseModel pmodel, AltaRicaNode node, Var var) {
		
		AltaRicaDomain domain;
		VarType type = var.getType();
		
		var.setNode(node);
		
		if (type.getVarType() == VarType.DOMAIN) {
			
			String id = type.getDomain();
			
			domain = pmodel.getDomain(id);
			
			if (domain == null) {
				
				System.out.println("Unknown domain: " + id);
			}
			
			var.setType(domain.getType());
		}
	}
	
	private void compileTransitions(AltaRicaNode node) {		
		
		List<Event> elst;

		Expression expr;
		
		for (Trans t : trans_list) {

			List<Event> clst = new LinkedList<Event>();
			
			expr = t.getGuard();
			
			compileExpression(node,expr);
			
			compileUpdates(node,t.getUpdates(),false);
			
			elst = t.getEvent();
			
			for (Event e : elst) {
				
				clst.add(node.getEvent(e));
			}

			if (clst.isEmpty())
				System.out.println("Missing Event");
			
			t.setEvents(clst);
		}
	}
	
	private void compileUpdates(AltaRicaNode node, List<UpdateExpression> lst, boolean isInit) {
		
		Var updated = null;
		
		for (UpdateExpression u : lst) {
			
			updated = u.getVar();
			
			if (updated.isLocal() == false) {
				if (isInit == false) {
					System.out.println("Error updating remote var " + updated.getId());
				}
				compileRemoteVar(node,updated,isInit,true);
			} else {
				updated = node.getStateVar(updated.getId()); 
			}
			
			if (updated == null) {
				System.out.println("Error: non state variable update: " + u.getVar().getId());
			} else {
				u.setVar(updated);
				compileExpression(node,u.getExpression());
			}
		}
	}
	
	private void compileExpression(AltaRicaNode node, Expression expr) {
		
		BinaryTree tree = expr.getExpression();
		
		compileExpressionTree(node,tree);
	}
	
	private void compileExpressionTree(AltaRicaNode node, BinaryTree tree) {
		
		Var var = null;
		
		LinkedList<BinaryTree> list = new LinkedList<BinaryTree>();
		
		list.add(tree);
		
		while (list.isEmpty() != true ) {
		
			tree = list.poll();
					
			Object value = tree.getValue();
		
			if (value instanceof Var) {
			
				var = (Var) value;
			
				if (var.isLocal() == false) {
					compileRemoteVar(node,var,true,true);
				} else {
					var = node.getVar(((Var) value).getId());
				}
				
				if (var == null) {
					tree.setValue(((Var) value).getId());
				} else {
					tree.setValue(var);
				}
			}
		
			if (tree.getLeft() != null)
				list.addLast(tree.getLeft());
			
			if (tree.getRight() != null)
				list.addLast(tree.getRight());
		}
	}
	
	private void compileRemoteVar(AltaRicaNode node, Var var, boolean checkVisibility, boolean link) {
		
		Var remoteVar = null;
		AltaRicaNode sub = node;
		List<String> lst = var.getSubs();
		
		for (String str : lst) {
			sub = sub.getSub(str);
			if (sub == null) {
				System.out.println("Missing Sub " + str);
				return ;
			}
		}
		
		remoteVar = sub.getVar(var.getName());
		
		if (remoteVar == null) {
			System.out.println("Missing Sub Var:" + var.getId() + " in: " + sub.getName());
			return ;
		}
		
		if (checkVisibility) {
			if (remoteVar.isPrivate()) {
				System.out.println("Error: manipulating private var: " + remoteVar);
			} else if (remoteVar.isParent() & lst.size() != 1) {
				System.out.println("Error: manipulating parent var: " + remoteVar + " in non parent node.");
			}
		}
		var.setRemoteVar(remoteVar);
		
		if (link == true)
			remoteVar.addRemoteReference(node);
	}
	
	private void compileRelationExpression(AltaRicaNode node, BinaryTree tree) {
		
		Var var = null;
		
		LinkedList<BinaryTree> list = new LinkedList<BinaryTree>();
		
		list.add(tree);
		
		while (list.isEmpty() != true ) {
		
			tree = list.poll();
					
			Object value = tree.getValue();
		
			if (value instanceof Var) {
			
				var = (Var) value;
			
				if (var.isLocal() == false && var.getSubs().size() > 1) {
					String str = var.getSubs().remove(0);
					compileRemoteVar(node,var,false,false);
					var.getSubs().add(0, str);
				} else {
					var = node.getVar(((Var) value).getId());
				}
				
				if (var == null) {
					tree.setValue(((Var) value).getId());
				} else {
					tree.setValue(var);
				}
			}
		
			if (tree.getLeft() != null)
				list.addLast(tree.getLeft());
			
			if (tree.getRight() != null)
				list.addLast(tree.getRight());
		}
	}
	
	public String toString(){
		
		String str = new String("node " + name + "\n");
		
		str += "   sub\n";
		for (String s[] : sub_list)
			str += "     " + s[0] + " : " + s[1] + ";\n";
		
		str += "   state\n";
		for (Var s : state_list)
			str += "     " + s + ";\n";
		
		str += "   flow\n";
		for (Var s : flow_list)
			str += "     " + s + ";\n";
		
		str += "   event\n" + "     ";
		for (Event e : event_list)
			str += e;
		str += ";\n";
		
		str += "   trans\n";
		for (Trans t : trans_list)
			str += "     " + t.toString() + ";\n";
		
		str += "   init\n";
		for (UpdateExpression u : init_list)
			str += "     " + u + ";\n" ;
		
		str += "   assert\n";
		for (Expression e : assert_list)
			str += "     " + e + ";\n" ;
				
		str += "edon\n";
		
		return str;
	}
}
